{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Week 4: Using real world data\n",
    "\n",
    "Welcome! So far you have worked exclusively with generated data. This time you will be using the [Daily Minimum Temperatures in Melbourne](https://github.com/jbrownlee/Datasets/blob/master/daily-min-temperatures.csv) dataset which contains data of the daily minimum temperatures recorded in Melbourne from 1981 to 1990. In addition to be using Tensorflow's layers for processing sequence data such as Recurrent layers or LSTMs you will also use Convolutional layers to improve the model's performance.\n",
    "\n",
    "- All cells are frozen except for the ones where you need to submit your solutions or when explicitly mentioned you can interact with it.\n",
    "\n",
    "- You can add new cells to experiment but these will be omitted by the grader, so don't rely on newly created cells to host your solution code, use the provided places for this.\n",
    "\n",
    "- You can add the comment # grade-up-to-here in any graded cell to signal the grader that it must only evaluate up to that point. This is helpful if you want to check if you are on the right track even if you are not done with the whole assignment. Be sure to remember to delete the comment afterwards!\n",
    "\n",
    "- Avoid using global variables unless you absolutely have to. The grader tests your code in an isolated environment without running all cells from the top. As a result, global variables may be unavailable when scoring your submission. Global variables that are meant to be used will be defined in UPPERCASE.\n",
    "\n",
    "- To submit your notebook, save it and then click on the blue submit button at the beginning of the page.\n",
    "\n",
    "Let's get started!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "deletable": false,
    "editable": false,
    "id": "56XEQOGknrAk",
    "outputId": "6b8ab5ea-ed49-40d2-a27c-d2ba08710ba0",
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "import csv\n",
    "import pickle\n",
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "outputs": [],
   "source": [
    "import unittests"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Begin by looking at the structure of the csv that contains the data:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "DATA_PATH = './data/daily-min-temperatures.csv'\n",
    "\n",
    "with open(DATA_PATH, 'r') as csvfile:\n",
    "    print(f\"Header looks like this:\\n\\n{csvfile.readline()}\")    \n",
    "    print(f\"First data point looks like this:\\n\\n{csvfile.readline()}\")\n",
    "    print(f\"Second data point looks like this:\\n\\n{csvfile.readline()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, each data point is composed of the date and the recorded minimum temperature for that date.\n",
    "\n",
    "\n",
    "In the first exercise you will code a function to read the data from the csv but for now run the next cell to load a helper function to plot the time series."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "id": "sLl52leVp5wU",
    "tags": []
   },
   "outputs": [],
   "source": [
    "def plot_series(time, series, format=\"-\", start=0, end=None):\n",
    "    \"\"\"Plot the series\"\"\"\n",
    "    plt.plot(time[start:end], series[start:end], format)\n",
    "    plt.xlabel(\"Time\")\n",
    "    plt.ylabel(\"Value\")\n",
    "    plt.grid(True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Parsing the raw data\n",
    "\n",
    "### Exercise 1: parse_data_from_file\n",
    "\n",
    "Now you need to read the data from the csv file. To do so, complete the `parse_data_from_file` function.\n",
    "\n",
    "A couple of things to note:\n",
    "\n",
    "- You are encouraged to use the function [`np.loadtxt`](https://numpy.org/doc/stable/reference/generated/numpy.loadtxt.html) to load the data. Make sure to check out the documentation to learn about useful parameters. \n",
    "- The `times` list should contain every timestep (starting at zero), which is just a sequence of ordered numbers with the same length as the `temperatures` list.\n",
    "- The values of the `temperatures` should be of `float` type. Make sure to select the correct column to read with `np.loadtxt`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 388
    },
    "deletable": false,
    "id": "NcG9r1eClbTh",
    "outputId": "7acf6ba9-e852-4f06-e06e-b0ff1b9e2ddd",
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "# GRADED FUNCTION: parse_data\n",
    "def parse_data_from_file(filename):\n",
    "    \"\"\"Parse data from csv file\n",
    "\n",
    "    Args:\n",
    "        filename (str): complete path to file (path + filename)\n",
    "\n",
    "    Returns:\n",
    "        (np.ndarray, np.ndarray): arrays of timestamps and values of the time series\n",
    "    \"\"\"    \n",
    "    ### START CODE HERE\n",
    "    # Load the temperatures using np.loadtxt. Remember you want to skip the first \n",
    "    # row, since it's headers. Make sure to use the correct column of the csv file.\n",
    "    temperatures = None\n",
    "    times = None # Create the time steps. \n",
    "    ### END CODE HERE\n",
    "        \n",
    "    return times, temperatures"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, use this function to create the timestamps, `TIME`, and the time series, `SERIES`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "TIME, SERIES = parse_data_from_file(DATA_PATH)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Plot the series!\n",
    "plt.figure(figsize=(10, 6))\n",
    "plot_series(TIME, SERIES)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Expected Output:**\n",
    "<div>\n",
    "<img src=\"images/temp-series.png\" width=\"650\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "outputs": [],
   "source": [
    "# Test your code!\n",
    "unittests.test_parse_data_from_file(parse_data_from_file)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Defining some useful global variables\n",
    "\n",
    "Next, you will define some global variables that will come used throughout the assignment. Feel free to reference them in the upcoming exercises:\n",
    "\n",
    "\n",
    "`SPLIT_TIME`: time index to split between train and validation sets\n",
    "\n",
    "`WINDOW_SIZE`: length od the window to use for smoothing the series\n",
    "\n",
    "`BATCH_SIZE`: batch size for training the model\n",
    "\n",
    "`SHUFFLE_BUFFER_SIZE`: number of elements from the dataset used to sample for a new shuffle of the dataset. For more information about the use of this variable you can take a look at the [docs](https://www.tensorflow.org/api_docs/python/tf/data/Dataset#shuffle).\n",
    "\n",
    "**A note about grading:**\n",
    "\n",
    "**When you submit this assignment for grading these same values for these globals will be used so make sure that all your code works well with these values. After submitting and passing this assignment, you are encouraged to come back here and play with these parameters to see the impact they have in the classification process. Since this next cell is frozen, you will need to copy the contents into a new cell and run it to overwrite the values for these globals.** \n",
    "\n",
    "The next cell will use your function to compute the `times` and `temperatures` and will save these as numpy arrays within the `G` dataclass. This cell will also plot the time series:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "# Save all global variables\n",
    "SPLIT_TIME = 2500\n",
    "WINDOW_SIZE = 64\n",
    "BATCH_SIZE = 256\n",
    "SHUFFLE_BUFFER_SIZE = 1000"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Processing the data\n",
    "\n",
    "Since you already coded the `train_val_split` and `windowed_dataset` functions during past week's assignments, this time they are provided for you. Notice that like in week 3, the `windowed_dataset` function has an extra step, which expands the series to have an extra dimension. This is done because you will be working with Conv layers which expect the dimensionality of its inputs to be 3 (including the batch dimension)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "def train_val_split(time, series):\n",
    "    \"\"\" Splits time series into train and validations sets\"\"\"\n",
    "    time_train = time[:SPLIT_TIME]\n",
    "    series_train = series[:SPLIT_TIME]\n",
    "    time_valid = time[SPLIT_TIME:]\n",
    "    series_valid = series[SPLIT_TIME:]\n",
    "\n",
    "    return time_train, series_train, time_valid, series_valid"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Split the dataset\n",
    "time_train, series_train, time_valid, series_valid = train_val_split(TIME, SERIES)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "id": "lJwUUZscnG38",
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "def windowed_dataset(series, window_size):\n",
    "    \"\"\"Creates windowed dataset\"\"\"\n",
    "    series = tf.expand_dims(series, axis=-1)\n",
    "    dataset = tf.data.Dataset.from_tensor_slices(series)\n",
    "    dataset = dataset.window(window_size + 1, shift=1, drop_remainder=True)\n",
    "    dataset = dataset.flat_map(lambda window: window.batch(window_size + 1))\n",
    "    dataset = dataset.shuffle(SHUFFLE_BUFFER_SIZE)\n",
    "    dataset = dataset.map(lambda window: (window[:-1], window[-1]))\n",
    "    dataset = dataset.batch(BATCH_SIZE).prefetch(1)\n",
    "    return dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "id": "lJwUUZscnG38",
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Apply the transformation to the training set\n",
    "train_dataset = windowed_dataset(series_train, window_size=WINDOW_SIZE)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Defining the model architecture\n",
    "\n",
    "### Exercise 2: create_uncompiled_model\n",
    "\n",
    "Now that you have a function that will process the data before it is fed into your neural network for training, it is time to define your model architecture. Just as in last week's assignment you will do the layer definition and compilation in two separate steps. Begin by completing the `create_uncompiled_model` function below.\n",
    "\n",
    "This is done so you can reuse your model's layers for the learning rate adjusting and the actual training.\n",
    "\n",
    "**Hint:**\n",
    "\n",
    "- Remember that the original dataset was expanded, so account for this when setting the shape of the `tf.keras.Input`\n",
    "\n",
    "- No `Lambda` layers are required\n",
    "- Use a combination of `Conv1D` and `LSTM` layers, followed by `Dense`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "# GRADED FUNCTION: create_uncompiled_model\n",
    "def create_uncompiled_model():\n",
    "    \"\"\"Define uncompiled model\n",
    "\n",
    "    Returns:\n",
    "        tf.keras.Model: uncompiled model\n",
    "    \"\"\"\n",
    "    ### START CODE HERE ###\n",
    "    \n",
    "    model = tf.keras.models.Sequential([ \n",
    "        tf.keras.Input(None), # Set the correct input shape for the model\n",
    "\t\t\n",
    "    ]) \n",
    "    \n",
    "    ### END CODE HERE ###\n",
    "    return model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The next cell allows you to check the number of total and trainable parameters of your model and prompts a warning in case these exceeds those of a reference solution, this serves the following 3 purposes listed in order of priority:\n",
    "\n",
    "- Helps you prevent crashing the kernel during training.\n",
    "\n",
    "- Helps you avoid longer-than-necessary training times.\n",
    "\n",
    "- Provides a reasonable estimate of the size of your model. In general you will usually prefer smaller models given that they accomplish their goal successfully.\n",
    "\n",
    "**Notice that this is just informative** and may be very well below the actual limit for size of the model necessary to crash the kernel. So even if you exceed this reference you are probably fine. However, **if the kernel crashes during training or it is taking a very long time and your model is larger than the reference, come back here and try to get the number of parameters closer to the reference.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Get your uncompiled model\n",
    "uncompiled_model = create_uncompiled_model()\n",
    "\n",
    "# Check the parameter count against a reference solution\n",
    "unittests.parameter_count(uncompiled_model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "outputs": [],
   "source": [
    "example_batch = train_dataset.take(1)\n",
    "\n",
    "try:\n",
    "\tpredictions = uncompiled_model.predict(example_batch, verbose=False)\n",
    "except:\n",
    "\tprint(\"Your model is not compatible with the dataset you defined earlier. Check that the loss function and last layer are compatible with one another.\")\n",
    "else:\n",
    "\tprint(\"Your current architecture is compatible with the windowed dataset! :)\")\n",
    "\tprint(f\"predictions have shape: {predictions.shape}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Expected output:**\n",
    "\n",
    "```\n",
    "Your current architecture is compatible with the windowed dataset! :)\n",
    "predictions have shape: (NUM_BATCHES, 1)\n",
    "```\n",
    "Where `NUM_BATCHES` is the number of batches you have set to your dataset."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "outputs": [],
   "source": [
    "# Test your code!\n",
    "unittests.test_create_uncompiled_model(create_uncompiled_model)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can also print a summary of your model to see what the architecture looks like. This can be useful to get a sense of how big your model is."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "outputs": [],
   "source": [
    "uncompiled_model.summary()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Adjusting the learning rate - (Optional Exercise)\n",
    "\n",
    "As you saw in the lectures, you can leverage Tensorflow's callbacks to dinamically vary the learning rate during training. This can be helpful to get a better sense of which learning rate better acommodates to the problem at hand. This is the same function you had on Week 3 Assignment, feel free to reuse it.\n",
    "\n",
    "**Notice that this is only changing the learning rate during the training process to give you an idea of what a reasonable learning rate is and should not be confused with selecting the best learning rate, this is known as hyperparameter optimization and it is outside the scope of this course.**\n",
    "\n",
    "For the optimizers you can try out:\n",
    "\n",
    "- tf.keras.optimizers.Adam\n",
    "- tf.keras.optimizers.SGD with a momentum of 0.9"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "def adjust_learning_rate(dataset):\n",
    "    \"\"\"Fit model using different learning rates\n",
    "\n",
    "    Args:\n",
    "        dataset (tf.data.Dataset): train dataset\n",
    "\n",
    "    Returns:\n",
    "        tf.keras.callbacks.History: callback history\n",
    "    \"\"\"\n",
    "\n",
    "    model = create_uncompiled_model()\n",
    "    \n",
    "    lr_schedule = tf.keras.callbacks.LearningRateScheduler(lambda epoch: 1e-5 * 10**(epoch / 20))\n",
    "    \n",
    "    ### START CODE HERE ###\n",
    "    \n",
    "    # Select your optimizer\n",
    "    optimizer = None\n",
    "    \n",
    "    # Compile the model passing in the appropriate loss\n",
    "    model.compile(loss=None,\n",
    "                  optimizer=optimizer, \n",
    "                  metrics=[\"mae\"]) \n",
    "    \n",
    "    ### END CODE HERE ###\n",
    "\n",
    "    history = model.fit(dataset, epochs=100, callbacks=[lr_schedule])\n",
    "    \n",
    "    return history"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Run the training with dynamic LR\n",
    "lr_history = adjust_learning_rate(train_dataset)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 290
    },
    "deletable": false,
    "editable": false,
    "id": "vVcKmg7Q_7rD",
    "outputId": "27cf16ae-eb85-47c3-fc86-18e72e528619",
    "tags": []
   },
   "outputs": [],
   "source": [
    "plt.semilogx(lr_history.history[\"learning_rate\"], lr_history.history[\"loss\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Compiling the model\n",
    "\n",
    "### Exercise 3: create_model\n",
    "\n",
    "Now, it is time to do the actual training that will be used to forecast the time series. For this, complete the `create_model` function below.\n",
    "\n",
    "Notice that you are reusing the architecture you defined in the `create_uncompiled_model` earlier. Now you only need to compile this model using the appropriate loss, optimizer (and learning rate). If you completed the optional exercise, you should have a better idea of what a good learning rate would be.\n",
    "\n",
    "**Hints:**\n",
    "\n",
    "- The training should be really quick so if you notice that each epoch is taking more than a few seconds, consider trying a different architecture.\n",
    "\n",
    "\n",
    "- If after the first epoch you get an output like this: loss: nan - mae: nan it is very likely that your network is suffering from exploding gradients. This is a common problem if you used SGD as optimizer and set a learning rate that is too high. If you encounter this problem consider lowering the learning rate or using Adam with the default learning rate."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "# GRADED FUNCTION: create_model\n",
    "def create_model():\n",
    "    \"\"\"Creates and compiles the model\n",
    "\n",
    "    Returns:\n",
    "        tf.keras.Model: compiled model\n",
    "    \"\"\"\n",
    "    \n",
    "    model = create_uncompiled_model()\n",
    "\n",
    "    ### START CODE HERE ###\n",
    "\n",
    "    model.compile(loss=None,\n",
    "                  optimizer=None,\n",
    "                  metrics=[\"mae\"])  \n",
    "    \n",
    "\n",
    "    ### END CODE HERE ###\n",
    "\n",
    "    return model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Save an instance of the model\n",
    "model = create_model()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "outputs": [],
   "source": [
    "# Test your code!\n",
    "unittests.test_create_model(create_model)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you passed the unittests, go ahead and train your model by running the cell below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "outputs": [],
   "source": [
    "# Train it\n",
    "history = model.fit(train_dataset, epochs=50)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now plot the training loss so you can monitor the learning process."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Plot the training loss for each epoch\n",
    "\n",
    "loss = history.history['loss']\n",
    "\n",
    "epochs = range(len(loss))\n",
    "\n",
    "plt.plot(epochs, loss, 'r', label='Training loss')\n",
    "plt.title('Training loss')\n",
    "plt.legend(loc=0)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Evaluating the forecast\n",
    "\n",
    "Now it is time to evaluate the performance of the forecast. For this you can use the `compute_metrics` function that you coded in a previous assignment:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "def compute_metrics(true_series, forecast):\n",
    "    \"\"\"Computes MSE and MAE metrics for the forecast\"\"\"\n",
    "    mse = tf.keras.losses.MSE(true_series, forecast)\n",
    "    mae = tf.keras.losses.MAE(true_series, forecast)\n",
    "    return mse, mae"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "At this point only the model that will perform the forecast is ready but you still need to compute the actual forecast.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Faster model forecasts\n",
    "\n",
    "In the previous weeks you used a for loop to compute the forecasts for every point in the sequence. This approach is valid but there is a more efficient way of doing the same thing by using batches of data. The code to implement this is provided in the `model_forecast` below. Notice that the code is very similar to the one in the `windowed_dataset` function with the differences that:\n",
    "- The dataset is windowed using `window_size` rather than `window_size + 1`\n",
    "- No shuffle should be used\n",
    "- No need to split the data into features and labels\n",
    "- A model is used to predict batches of the dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "id": "4XwGrf-A_wF0",
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "def model_forecast(model, series, window_size):\n",
    "    \"\"\"Generates a forecast using your trained model\"\"\"\n",
    "    ds = tf.data.Dataset.from_tensor_slices(series)\n",
    "    ds = ds.window(window_size, shift=1, drop_remainder=True)\n",
    "    ds = ds.flat_map(lambda w: w.batch(window_size))\n",
    "    ds = ds.batch(32).prefetch(1)\n",
    "    forecast = model.predict(ds)\n",
    "    return forecast"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 389
    },
    "deletable": false,
    "editable": false,
    "id": "PrktQX3hKYex",
    "outputId": "1914662d-6bdd-4e17-8697-8f5a29e89b87",
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Compute the forecast for the validation dataset. Remember you need the last WINDOW SIZE values to make the first prediction\n",
    "rnn_forecast = model_forecast(model, SERIES[SPLIT_TIME-WINDOW_SIZE:-1], WINDOW_SIZE).squeeze()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 389
    },
    "deletable": false,
    "editable": false,
    "id": "PrktQX3hKYex",
    "outputId": "1914662d-6bdd-4e17-8697-8f5a29e89b87",
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Plot the forecast\n",
    "plt.figure(figsize=(10, 6))\n",
    "plot_series(time_valid, series_valid)\n",
    "plot_series(time_valid, rnn_forecast)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "mse, mae = compute_metrics(series_valid, rnn_forecast)\n",
    "\n",
    "print(f\"mse: {mse:.2f}, mae: {mae:.2f} for forecast\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**To pass this assignment your forecast should achieve a MSE of 6 or less and a MAE of 2 or less.**\n",
    "\n",
    "If your forecast didn't achieve this threshold try re-training your model with a different architecture (you will need to re-run both `create_uncompiled_model` and `create_model` functions) or tweaking the optimizer's parameters.\n",
    "\n",
    "If your forecast did achieve these thresholds run the following cell to save the metrics in a binary file which will be used for grading. After doing so, submit your assignment for grading."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Save metrics into a dictionary\n",
    "metrics_dict = {\n",
    "    \"mse\": float(mse),\n",
    "    \"mae\": float(mae)\n",
    "}\n",
    "\n",
    "# Save your metrics in a binary file \n",
    "with open('metrics.pkl', 'wb') as f:\n",
    "    pickle.dump(metrics_dict, f)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Congratulations on finishing this week's assignment!**\n",
    "\n",
    "You have successfully implemented a neural network capable of forecasting time series leveraging a combination of Tensorflow's layers such as Convolutional and LSTMs! This resulted in a forecast that surpasses all the ones you did previously.\n",
    "\n",
    "**By finishing this assignment you have finished the specialization! Give yourself a pat on the back!!!**"
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "collapsed_sections": [],
   "name": "C4_W4_Assignment_Solution.ipynb",
   "provenance": [],
   "toc_visible": true
  },
  "grader_version": "1",
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.0rc1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
